﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using XmlStudio.Interfaces;
using XmlStudio.DataLayer;
using System.Xml.Schema;
using System.Xml;
using System.IO;

namespace XmlStudio.Models {
    internal class XsdModel : XmlModel, IXsdModel {
        #region Fields
        private List<BigXmlNode> namedAttributes = new List<BigXmlNode>();
        private List<BigXmlNode> namedSimpleTypes = new List<BigXmlNode>();
        private List<BigXmlNode> namedComplexTypes = new List<BigXmlNode>();
        private List<BigXmlNode> namedAttributeGroups = new List<BigXmlNode>();
        private List<BigXmlNode> namedElements = new List<BigXmlNode>();
        private List<BigXmlNode> namedKeyElements = new List<BigXmlNode>();
        private List<BigXmlNode> namedUniqueElements = new List<BigXmlNode>();
        #endregion

        #region Constructors
        public XsdModel() {
            this.Mode = ApplicationMode.Xsd;
            this.XsPrefix = "xs";
        }
        #endregion

        #region Properties
        /// <summary>
        /// Gets the xsd prefix.
        /// </summary>
        public string XsPrefix { get; private set; }
        #endregion

        #region IXsdModel Members
        public void CheckXsd() {
            var xml = this.GetRawXml();
            using(var ms = new MemoryStream(Encoding.Default.GetBytes(xml))) {
                var schema = XmlSchema.Read(XmlReader.Create(ms), ValidationCallBack);
            }
        }

        public override BigXmlNode GetRootElement() {
            var root = base.GetRootElement();
            this.XsPrefix = root.Prefix;
            return root;
        }

        private void ValidationCallBack(object sender, ValidationEventArgs e) {
            this.OnValidating(e);
        }

        /// <summary>
        /// Creates element from XMLSchema namespace with xs prefix without any simpleTypes.
        /// </summary>
        /// <param name="name">Local name of the element to create.</param>
        /// <returns>Reference to newly created element.</returns>
        public BigXmlNode CreateXsdElement(string name) {
            return XsdFactory.CreateElement(this, name);
        }

        public static IEnumerable<XsdStructureItem> GetContent(XsdElement element, XsdElement? parent = null) {
            return XsdStructure.GetContent(element, parent);
        }

        public BigXmlNode CreateElementAll(string id, int? maxOccurs, int? minOccurs, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementAll(this, id, maxOccurs, minOccurs, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementAnnotation(string id, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementAnnotation(this, id, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementAny(string id, string maxOccurs, string minOccurs, string @namespace, ProcessContents? processContents, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementAny(this, id, maxOccurs, minOccurs, @namespace, processContents, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementAnyAttribute(string id, string @namespace, ProcessContents? processContents, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementAnyAttribute(this, id, @namespace, processContents, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementAppInfo(string source, XsdElement? parent) {
            return XsdFactory.CreateElementAppInfo(this, source, parent);
        }

        public BigXmlNode CreateElementAttribute(string @default, string @fixed, Form? form, string id, string name, string @ref, string type, Use? use, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementAttribute(this, @default, @fixed, form, id, name, @ref, type, use, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementAttributeGroup(string id, string name, string @ref, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementAttributeGroup(this, id, name, @ref, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementChoice(string id, string maxOccurs, string minOccurs, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementChoice(this, id, maxOccurs, minOccurs, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementComplexContent(string id, bool? mixed, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementComplexContent(this, id, mixed, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementComplexType(string id, string name, bool? @abstract, bool? mixed, string block, string final, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementComplexType(this, id, name, @abstract, mixed, block, final, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementDocumentation(string source, string lang, XsdElement? parent) {
            return XsdFactory.CreateElementDocumentation(this, source, lang, parent);
        }

        public BigXmlNode CreateElementElement(string id, string name, string @ref, string type, string substitutionGroup, string @default, string @fixed, Form? form, string maxOccurs, string minOccurs, bool? nillable, bool? @abstract, string block, string final, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementElement(this, id, name, @ref, type, substitutionGroup, @default, @fixed, form, maxOccurs, minOccurs, nillable, @abstract, block, final, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementExtension(string id, string @base, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementExtension(this, id, @base, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementField(string id, string xpath, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementField(this, id, xpath, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementGroup(string id, string name, string @ref, string maxOccurs, string minOccurs, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementGroup(this, id, name, @ref, maxOccurs, minOccurs, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementImport(string id, string @namespace, string schemaLocation, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementImport(this, id, @namespace, schemaLocation, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementInclude(string id, string schemaLocation, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementInclude(this, id, schemaLocation, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementKey(string id, string name, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementKey(this, id, name, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementKeyRef(string id, string name, string refer, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementKeyRef(this, id, name, refer, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementList(string id, string itemType, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementList(this, id, itemType, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementNotation(string id, string name, string @public, string system, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementNotation(this, id, name, @public, system, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementRedefine(string id, string schemaLocation, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementRedefine(this, id, schemaLocation, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementRestriction(string id, string @base, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementRestriction(this, id, @base, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementSchema(string id, Form? attributeFormDefault, Form? elementFormDefault, string blockDefault, string finalDefault, string targetNamespace, string version, string xmlns, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementSchema(this, id, attributeFormDefault, elementFormDefault, blockDefault, finalDefault, targetNamespace, version, xmlns, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementSelector(string id, string xpath, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementSelector(this, id, xpath, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementSequence(string id, string maxOccurs, string minOccurs, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementSequence(this, id, maxOccurs, minOccurs, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementSimpleContent(string id, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementSimpleContent(this, id, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementSimpleType(string id, string name, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementSimpleType(this, id, name, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementUnion(string id, string memberTypes, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementUnion(this, id, memberTypes, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementUnique(string id, string name, IEnumerable<IBigXmlAttribute> additionalAttributes, XsdElement? parent) {
            return XsdFactory.CreateElementUnique(this, id, name, additionalAttributes, parent);
        }

        public BigXmlNode CreateElementEnumeration(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.enumeration, value);
        }

        public BigXmlNode CreateElementFractionDigits(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.fractionDigits, value);
        }

        public BigXmlNode CreateElementLength(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.length, value);
        }

        public BigXmlNode CreateElementMaxExclusive(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.maxExclusive, value);
        }

        public BigXmlNode CreateElementMaxInclusive(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.maxInclusive, value);
        }

        public BigXmlNode CreateElementMaxLength(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.maxLength, value);
        }

        public BigXmlNode CreateElementMinExclusive(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.minExclusive, value);
        }

        public BigXmlNode CreateElementMinInclusive(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.minInclusive, value);
        }

        public BigXmlNode CreateElementMinLength(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.minLength, value);
        }

        public BigXmlNode CreateElementPattern(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.pattern, value);
        }

        public BigXmlNode CreateElementTotalDigits(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.totalDigits, value);
        }

        public BigXmlNode CreateElementWhiteSpace(string value) {
            return XsdFactory.CreateFacet(this, XsdFacet.whiteSpace, value);
        }
        #endregion

        #region Lists of created elements
        #region Properties
        /// <summary>
        /// Gets the collection of names of named attributes.
        /// </summary>
        public IEnumerable<string> NamedAttributes {
            get {
                return from n in this.ListNamedAttributes() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }

        /// <summary>
        /// Gets the collection of names of named simple types.
        /// </summary>
        public IEnumerable<string> NamedSimpleTypes {
            get {
                return from n in this.ListNamedSimpleTypes() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }

        /// <summary>
        /// Gets the collection of names of named complex types.
        /// </summary>
        public IEnumerable<string> NamedComplexTypes {
            get {
                return from n in this.ListNamedComplexTypes() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }

        /// <summary>
        /// Gets the collection of names of named attribute groups.
        /// </summary>
        public IEnumerable<string> NamedAttributeGroups {
            get {
                return from n in this.ListNamedAttributeGroups() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }

        /// <summary>
        /// Gets the collection of names of named groups.
        /// </summary>
        public IEnumerable<string> NamedGroups {
            get {
                return from n in this.ListNamedGroups() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }

        /// <summary>
        /// Gets the collection of names of named elements.
        /// </summary>
        public IEnumerable<string> NamedElements {
            get {
                return from n in this.ListNamedElements() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }

        /// <summary>
        /// Gets the collection of names of named key elements.
        /// </summary>
        public IEnumerable<string> NamedKeyElements {
            get {
                return from n in this.ListNamedKeyElements() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }

        /// <summary>
        /// Gets the collection of names of named unique elements.
        /// </summary>
        public IEnumerable<string> NamedUniqueElements {
            get {
                return from n in this.ListNamedUniqueElements() select n.Attributes[BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)].Value;
            }
        }
        #endregion

        /// <summary>
        /// Lists built-in xsd data types.
        /// </summary>
        /// <returns>Collection of built-in data types.</returns>
        public IEnumerable<XsdBuiltInDataType> ListBuiltInDataTypes() {
            return XsdDataTypeHelper.ListBuiltInXsdTypes();
        }

        /// <summary>
        /// Lists named simpleTypes in current schema.
        /// </summary>
        /// <returns>Collection of named simpleTypes.</returns>
        public IEnumerable<BigXmlNode> ListNamedAttributes() {
            var attributes = from n in this.Nodes.Values where n.LocalName == "attribute" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return attributes;
        }

        /// <summary>
        /// Lists named simple types in current schema.
        /// </summary>
        /// <returns>Collection of named simple types.</returns>
        public IEnumerable<BigXmlNode> ListNamedSimpleTypes() {
            var simpleTypes = from n in this.Nodes.Values where n.LocalName == "simpleType" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return simpleTypes;
        }

        /// <summary>
        /// Lists named complex types in current schema.
        /// </summary>
        /// <returns>Collection of named complex types.</returns>
        public IEnumerable<BigXmlNode> ListNamedComplexTypes() {
            var complexTypes = from n in this.Nodes.Values where n.LocalName == "complexType" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return complexTypes;
        }

        /// <summary>
        /// Lists named attribute groups in current schema.
        /// </summary>
        /// <returns>Collection of named attribute groups.</returns>
        public IEnumerable<BigXmlNode> ListNamedAttributeGroups() {
            var attributes = from n in this.Nodes.Values where n.LocalName == "attributeGroup" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return attributes;
        }

        /// <summary>
        /// Lists named groups in current schema.
        /// </summary>
        /// <returns>Collection of named groups.</returns>
        public IEnumerable<BigXmlNode> ListNamedGroups() {
            var attributes = from n in this.Nodes.Values where n.LocalName == "group" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return attributes;
        }

        /// <summary>
        /// Lists named elements in current schema.
        /// </summary>
        /// <returns>Collection of named elements.</returns>
        public IEnumerable<BigXmlNode> ListNamedElements() {
            var elements = from n in this.Nodes.Values where n.LocalName == "element" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return elements;
        }

        /// <summary>
        /// Lists named key elements in current schema.
        /// </summary>
        /// <returns>Collection of named key element.</returns>
        public IEnumerable<BigXmlNode> ListNamedKeyElements() {
            var keys = from n in this.Nodes.Values where n.LocalName == "key" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return keys;
        }

        /// <summary>
        /// Lists named unique element in current schema.
        /// </summary>
        /// <returns>Collection of named unique elements.</returns>
        public IEnumerable<BigXmlNode> ListNamedUniqueElements() {
            var unique = from n in this.Nodes.Values where n.LocalName == "unique" && n.Attributes.ContainsKey(BigXmlAttribute.CreateKey(string.Empty, "name", string.Empty)) select n;
            return unique;
        }
        #endregion
    }
}
